﻿using ICSharpCode.SharpZipLib.Zip;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Options;
using SimpleNugetServer.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace SimpleNugetServer
{
    public class NugetPackageSource : INugetPackageSource
    {
        private const string CONFIG_FILTER = "*.nupkg";
        private bool _Loaded = false;
        private bool _Loading = false;
        private bool _Disposed = false;
        private readonly ReaderWriterLockSlim _Locker = new ReaderWriterLockSlim();
        private readonly object _ReadloadSyncRoot = new object();
        private readonly object _DisposeSyncRoot = new object();
        private IHostingEnvironment _HostingEnvironment;
        private NugetOptions _NugetOptions;
        private IDictionary<string, NuspecSortedList> _Packages;
        private string _PackageDirectory;
        private List<string> _ApiKeys;

        public NugetPackageSource(IHostingEnvironment hostingEnvironment,IOptions<NugetOptions> options)
        {
            _HostingEnvironment = hostingEnvironment;
            _NugetOptions = options.Value;
            _Packages = new Dictionary<string, NuspecSortedList>();
            _ApiKeys = new List<string>();

            LoadApiKeys();
        }

        public string PackageDirectory
        {
            get
            {
                return _PackageDirectory;
            }
        }

        private string GetPackageDirectory()
        {
            var dirPath = Path.Combine(_HostingEnvironment.ContentRootPath, _NugetOptions.PackageDirectory);

            if (!Directory.Exists(dirPath))
            {
                return null;
            }

            DirectoryInfo dir = new DirectoryInfo(dirPath);
            return dir.FullName;
        }

        public NugetPackageSource LoadPackages()
        {
            _PackageDirectory = GetPackageDirectory();
            if (!string.IsNullOrEmpty(_PackageDirectory))
            {
                _Loaded = true;
                RetrieveNuspecs();
            }

            return this;
        }

        public IEnumerable<Nuspec> Find(QueryParameters parameters)
        {
            if (!_Loaded)
            {
                throw new InvalidOperationException($"Packages没有成功加载,无法读取.");
            }

            _Locker.EnterReadLock();
            try
            {
                var packages = _Packages;
                if (!(parameters.IncludePrerelease ?? false))
                {
                    packages = packages
                        .ToDictionary(m => m.Key, m =>
                        {
                            var list = m.Value.Where(o => !o.IsPrerelease()).ToList();
                            return new NuspecSortedList(list);
                        });
                }

                if (parameters.IsLatestVersion)
                {
                    packages = packages
                        .Where(o => o.Value.Count > 0)
                        .ToDictionary(m => m.Key, m => new NuspecSortedList(new Nuspec[] { m.Value.First() }));
                }

                if (!string.IsNullOrEmpty(parameters.SearchTerm))
                {
                    packages = packages.ToDictionary(
                        m => m.Key,
                        m =>
                        {
                            if (m.Key.IndexOf(parameters.SearchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
                            {
                                return m.Value;
                            }

                            IEnumerable<Nuspec> list = m.Value;

                            list = list.Where(n =>
                            {
                                return (!string.IsNullOrWhiteSpace(n.Metadata.Title) && n.Metadata.Title.IndexOf(parameters.SearchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
                                    || (!string.IsNullOrWhiteSpace(n.Metadata.Tags) && (n.Metadata.Tags).IndexOf(parameters.SearchTerm, StringComparison.OrdinalIgnoreCase) >= 0)
                                    || (!string.IsNullOrWhiteSpace(n.Metadata.Description) && n.Metadata.Description.IndexOf(parameters.SearchTerm, StringComparison.OrdinalIgnoreCase) >= 0);
                            });
                            return new NuspecSortedList(list);
                        });
                }

                if (!string.IsNullOrEmpty(parameters.TargetFramework))
                {
                    packages = packages.ToDictionary(
                        m => m.Key,
                        m =>
                        {
                            IEnumerable<Nuspec> list = m.Value;

                            // TODO: 目前不能很好的处理过滤
                            //list = list.Where(n =>
                            //{
                            //    var targetFrameworks = n.Metadata.DependencySets.Select(o => 
                            //    {
                            //        // .NETFramework4.6.1 转换成 .net461
                            //        // .NETStandard2.0 转换成.netstandard20
                            //        return o.TargetFramework.Replace("NETFramework", "net").Replace(".", string.Empty).ToLower();
                            //    }).ToList();
                            //    return n.TargetFrameworks.Contains(targetFramework, StringComparer.OrdinalIgnoreCase)
                            //        || targetFrameworks.Contains(targetFramework, StringComparer.OrdinalIgnoreCase);
                            //});
                            return new NuspecSortedList(list);
                        });
                }

                var nuspecList = packages.SelectMany(m => m.Value).ToList();
                return nuspecList.Skip(parameters.Skip ?? 0).Take(parameters.Take ?? 30);
            }
            finally
            {
                _Locker.ExitReadLock();
            }
        }

        public Nuspec FindPackagesById(string id, string version)
        {
            _Locker.EnterReadLock();
            try
            {
                NuspecSortedList list = null;
                if (_Packages.TryGetValue(id, out list))
                {
                    return list.FirstOrDefault(m => string.Equals(m.Metadata.Version, version, StringComparison.OrdinalIgnoreCase));
                }
                return null;
            }
            finally
            {
                _Locker.ExitReadLock();
            }
        }

        public void PutPackage(string apiKey, Stream fileStream)
        {
            if (!fileStream.CanSeek) throw new Exception("Stream必须是CanSeek");
            if (!_ApiKeys.Contains(apiKey)) throw new Exception("没有权限");

            using (var ms = new MemoryStream())
            {
                // 先把数据保存在内存中
                var buffer = new byte[8092];
                while (true)
                {
                    var count = fileStream.Read(buffer, 0, buffer.Length);
                    if (count == 0) break;
                    ms.Write(buffer, 0, count);
                }

                // 提取nuget文件信息， 注意这里用的是fileStream对象
                var nuspec = RetrieveNuspec(fileStream, null);

                // 保存文件
                var fileName = $"{nuspec.Metadata.Id}.{nuspec.Metadata.Version}.nupkg"; ;
                var path = Path.Combine(_PackageDirectory, fileName);
                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    //_Queue.Enqueue(path);
                    fs.Write(ms.ToArray());

                    if (nuspec != null)
                    {
                        string id = nuspec.Metadata.Id;
                        string version = nuspec.Metadata.Version;

                        NuspecSortedList list = null;
                        if (!_Packages.TryGetValue(id, out list))
                        {
                            list = new NuspecSortedList();
                            _Packages.Add(id, list);
                        }

                        var found = list.FirstOrDefault(m => string.Equals(m.Metadata.Version, version, StringComparison.OrdinalIgnoreCase));
                        if (found == null) list.Add(nuspec);
                    }
                }
            }
        }

        public IEnumerable<Nuspec> FindPackagesById(string id)
        {
            _Locker.EnterReadLock();
            try
            {
                NuspecSortedList list = null;
                if (_Packages.TryGetValue(id, out list))
                {
                    return list;
                }
                return new NuspecSortedList();
            }
            finally
            {
                _Locker.ExitReadLock();
            }
        }

        private void LoadApiKeys()
        {
            _ApiKeys.AddRange(_NugetOptions.ApiKeys.Values);
        }

        private void RetrieveNuspecs()
        {
            lock (_ReadloadSyncRoot)
            {
                if (_Loading) return;
                _Loading = true;
            }

            _Locker.EnterWriteLock();
            try
            {
                var packageFileNames = Directory.GetFiles(_PackageDirectory, CONFIG_FILTER, SearchOption.AllDirectories);
                foreach(var packageFileName in packageFileNames)
                {
                    using (var archiveFileStream = System.IO.File.Open(packageFileName, FileMode.Open, FileAccess.Read))
                    {
                        var nuspec = RetrieveNuspec(archiveFileStream, packageFileName);
                        if (nuspec != null)
                        {
                            string id = nuspec.Metadata.Id;
                            string version = nuspec.Metadata.Version;

                            NuspecSortedList list = null;
                            if (!_Packages.TryGetValue(id, out list))
                            {
                                list = new NuspecSortedList();
                                _Packages.Add(id, list);
                            }

                            var found = list.FirstOrDefault(m => string.Equals(m.Metadata.Version, version, StringComparison.OrdinalIgnoreCase));
                            if (found == null)
                            {
                                list.Add(nuspec);
                            }
                        }
                    }
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                _Locker.ExitWriteLock();
                lock (_ReadloadSyncRoot)
                {
                    _Loading = false;
                }
            }
        }

        private Nuspec RetrieveNuspec(Stream archiveFileStream, string nupkgPath)
        {
            long fileSize;
            byte[] fileHashBytes;
            using (var hashAlgorithm = SHA512.Create())
            {
                fileSize = archiveFileStream.Length;
                fileHashBytes = hashAlgorithm.ComputeHash(archiveFileStream);

                archiveFileStream.Position = 0;
                using (ZipInputStream zs = new ZipInputStream(archiveFileStream))
                {
                    Nuspec nuspec = null;
                    var targetFrameworks = new List<string>();
                    Regex regex = new Regex("^lib/(?<TARGETFRAMEWORK>.+?)(/.*)?$");

                    while (true)
                    {
                        var entry = zs.GetNextEntry();
                        if (entry == null) break;

                        if (entry.Name.EndsWith(".nuspec", StringComparison.Ordinal))
                        {
                            var nuspecContent = zs.ReadString(entry);

                            using (var sr = new StringReader(nuspecContent))
                            using (var reader = XmlReader.Create(sr))
                            {
                                XmlSerializer serializer = null;
                                var serializer2013 = new XmlSerializer(typeof(Nuspec2013));
                                var serializer2012 = new XmlSerializer(typeof(Nuspec2012));
                                var serializer2011 = new XmlSerializer(typeof(Nuspec2011));

                                if (serializer2013.CanDeserialize(reader))
                                {
                                    serializer = serializer2013;
                                }
                                else if (serializer2012.CanDeserialize(reader))
                                {
                                    serializer = serializer2012;
                                }
                                else if (serializer2011.CanDeserialize(reader))
                                {
                                    serializer = serializer2011;
                                }
                                else
                                {
                                    throw new NotSupportedException("不支持的 Namespace");
                                }

                                nuspec = (Nuspec)serializer.Deserialize(reader);
                                nuspec.Size = fileSize;
                                nuspec.Hash = Convert.ToBase64String(fileHashBytes);
                                nuspec.Path = nupkgPath;
                            }

                            // 继续遍历下一个entry
                            continue;
                        }

                        var mtEntryName = regex.Match(entry.Name);
                        if (mtEntryName.Success)
                        {
                            var targetFramework = mtEntryName.Groups["TARGETFRAMEWORK"].Value;
                            if (!targetFrameworks.Contains(targetFramework))
                            {
                                targetFrameworks.Add(targetFramework);
                            }
                        }
                    }

                    //if (nuspec == null) continue;
                    nuspec.TargetFrameworks = targetFrameworks.ToArray();

                    return nuspec;
                    //string id = nuspec.Metadata.Id;
                    //string version = nuspec.Metadata.Version;

                    //NuspecSortedList list = null;
                    //if (!_Packages.TryGetValue(id, out list))
                    //{
                    //    list = new NuspecSortedList();
                    //    _Packages.Add(id, list);
                    //}

                    //var found = list.FirstOrDefault(m => string.Equals(m.Metadata.Version, version, StringComparison.OrdinalIgnoreCase));
                    //if (found == null) list.Add(nuspec);
                }
            }
        }

        public static string CreateDependencyString(IEnumerable<NuspecDependencySet> sets)
        {
            var str = string.Join("|", sets.Select(CreateDependencyString));
            return str;
        }

        private static string CreateDependencyString(NuspecDependencySet set)
        {
            if (set.Dependencies == null || set.Dependencies.Count == 0)
            {
                if (set.TargetFramework != null)
                {
                    return $"::{FrameworkNameToMoniker(set.TargetFramework)}";
                }
            }

            if (set.TargetFramework == null)
            {
                return string.Join("|", set.Dependencies.Select(d => string.IsNullOrEmpty(d.Version) ? d.Id : $"{d.Id}:{d.Version}"));
            }

            return string.Join("|", set.Dependencies.Select(d => $"{d.Id}:{d.Version}:{FrameworkNameToMoniker(set.TargetFramework)}"));
        }

        private static string FrameworkNameToMoniker(string frameworkName)
        {
            const string netframeworkFrameworkName = ".NETFramework";
            const string netframeworkMonikerPrefix = "net";
            if (frameworkName.Contains(netframeworkFrameworkName))
            {
                var versionPart = frameworkName.Replace(netframeworkFrameworkName, string.Empty);
                return $"{netframeworkMonikerPrefix}{versionPart.Replace(".", string.Empty)}";
            }
            else
            {
                string fw = frameworkName.TrimStart('.').ToLower();
                return fw;
            }
        }

        /// <summary>
        /// 释放资源。
        /// </summary>
        public void Dispose()
        {
            lock (_DisposeSyncRoot)
            {
                if (_Disposed)
                {
                    return;
                }
            }

            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 析构函数。
        /// </summary>
        ~NugetPackageSource()
        {
            this.Dispose();
        }
    }
}
